package beerAgent;

import neuralNetwork.CTRNN;
import beerAgent.BeerModel.Scenario;
import evolutionaryProblems.Phenotype;
import fitnessFunctions.FitnessFunction;
import genotype.BeerGenotype;

public class BeerFitnessFunction implements FitnessFunction {

	private BeerParameters parameters;
	private BeerGame[] games;
	private BeerGame[] trainingIntercept, trainingDodge;
	private static final int INTERCEPT_TRAIN = 1;
	private static final int DODGE_TRAIN = 2;
	private static final int RANDOM = 3;
	private static final int INTERCEPT_ALL_TRAIN = 4;
	private int currentFitnessFunction = INTERCEPT_TRAIN;
	private double switchFitness = 1.0;

	public BeerFitnessFunction(BeerParameters parameters) {
		this.parameters = parameters;
		createTrainingGames();
		createGames();
	}

	private void createTrainingGames() {
		int nofTrainingMaps = 2;
		trainingDodge = new BeerGame[nofTrainingMaps];
		trainingIntercept = new BeerGame[nofTrainingMaps];

		for (int i = 0; i < trainingDodge.length; i++) {
			trainingDodge[i] = new BeerGame(5, 6, parameters.getHorizontalSpeed());
			trainingIntercept[i] = new BeerGame(1, 4, parameters.getHorizontalSpeed()); 
		}
	}

	private void createGames() {
		int nofMaps = parameters.getNofMaps(); 
		games = new BeerGame[nofMaps]; 
		for (int i = 0; i < nofMaps; i++) {
			BeerGame game = new BeerGame(1, 6, parameters.getHorizontalSpeed()); 
			games[i] = game;
		}
	}
	
	public int getCurrentFitnessFunction(){
		return currentFitnessFunction;
	}

	@Override
	public double calculateFitness(Phenotype phenotype) {
		int nofCatchLarge = 0;
		int nofCatchSmall = 0;
		int nofMissLarge = 0;
		int nofMissSmall = 0;
		int nofHitByLarge = 0;
		int iterations = parameters.getIterations();

		CTRNN network = new CTRNN(parameters.getNetworkLayout());
		Double[] weights = ((BeerGenotype)phenotype.getGenotype()).getArrayGenotype();
		
		network.setWeights(convertDtod(weights));
		
		BeerGame[] currentGames = null;
		
		switch (currentFitnessFunction) {
		case DODGE_TRAIN:
			currentGames = trainingDodge;
			break;
		case INTERCEPT_TRAIN:
			currentGames = trainingIntercept;
			break;
		case INTERCEPT_ALL_TRAIN:
			currentGames = games;
		default:
			currentGames = games;
			break;
		}

		for (BeerGame game : currentGames) {
			game.setDraw(false); 
			for (int i = 0; i < iterations; i ++){
				boolean[] sensors = game.getSensors(); 
				double[] outputs = network.update(sensors);
				

				int steps = getSteps(outputs);
				BeerModel.Action action = selectAction(outputs, steps);
				
				Scenario moveResult = game.move(action, steps);

				switch (moveResult) {
				case CATCH_LARGE:
					nofCatchLarge++;
					break;
				case CATCH_SMALL:
					nofCatchSmall++;
					break;
				case MISS_LARGE:
					nofMissLarge++;
					break;
				case MISS_SMALL:
					nofMissSmall++;
					break;
				case HIT_BY_LARGE:
					nofHitByLarge++;
					break;
				default:
					break;
				}
				
			}
			game.reset();
			
		}

		switch (currentFitnessFunction) {
		case INTERCEPT_TRAIN:
			return interceptTrainFitness(nofCatchSmall, nofMissSmall, nofCatchLarge, nofMissLarge, nofHitByLarge);
		case DODGE_TRAIN:
			return dodgeTrainFitness(nofCatchSmall, nofMissSmall, nofCatchLarge, nofMissLarge, nofHitByLarge);
		case INTERCEPT_ALL_TRAIN:
			return interCeptAllTrainFitness(nofCatchSmall, nofMissSmall, nofCatchLarge, nofMissLarge, nofHitByLarge);
		default:
			return currentFitnessFunction(nofCatchSmall, nofMissSmall, nofCatchLarge, nofMissLarge, nofHitByLarge);
		}

	}
	
	private double interCeptAllTrainFitness(int nofCatchSmall, int nofMissSmall, int nofCatchLarge, int nofMissLarge, int nofHitByLarge) {
		int countEatenAndDodged = nofCatchSmall + nofMissLarge;
		int countMissedAndHits = nofMissSmall + nofCatchLarge + nofHitByLarge;
		
		double fitness = ((double)(nofCatchLarge + nofCatchSmall)) / (countEatenAndDodged + countMissedAndHits);
		
		if (fitness>=this.switchFitness) 
			currentFitnessFunction = RANDOM;
		return fitness > 0 ? fitness : 0;
	}

	private double interceptTrainFitness(int nofCatchSmall, int nofMissSmall, int nofCatchLarge, int nofMissLarge, int nofHitByLarge) {
		int countEatenAndDodged = nofCatchSmall + nofMissLarge;
		int countMissedAndHits = nofMissSmall + nofCatchLarge + nofHitByLarge;
		double fitness = ((double)countEatenAndDodged)/(countEatenAndDodged + countMissedAndHits);
		if (fitness>=this.switchFitness) 
			currentFitnessFunction = RANDOM;
		return fitness > 0 ? fitness : 0;
	}

	private double dodgeTrainFitness(int nofCatchSmall, int nofMissSmall, int nofCatchLarge, int nofMissLarge, int nofHitByLarge) {
		int countEatenAndDodged = nofCatchSmall + nofMissLarge;
		int countMissedAndHits = nofMissSmall + nofCatchLarge;
		double fitness = 1.0 / (1.0 + nofCatchLarge + nofHitByLarge);
		if (fitness>=this.switchFitness) currentFitnessFunction = RANDOM;
		return fitness > 0 ? fitness : 0;
	}

	private double currentFitnessFunction(int nofCatchSmall, int nofMissSmall, int nofCatchLarge, int nofMissLarge, int nofHitByLarge) {
		int countEatenAndDodged = nofCatchSmall + nofMissLarge;
		int countMissedAndHits = nofMissSmall + nofCatchLarge + nofHitByLarge;
		double fitness = ((double)(countEatenAndDodged-countMissedAndHits)) / (countEatenAndDodged+countMissedAndHits);
		if (fitness < 0 ){
			return 0; 
		} else {
			return fitness; 
		}
	}

	public int getSteps(double[] outputs) {
		double left = outputs[0];
		double right = outputs[1];
		double totalOutput = left + right;
		double diff = Math.abs(left-right);
		double steps = diff/totalOutput;
		
		if (steps < 0.2) return 0;
		if (steps < 0.4) return 1;
		if (steps < 0.6) return 2;
		if (steps < 0.8) return 3;
		else return 4;
	}

	public BeerModel.Action selectAction(double[] outputs, int steps){
		if (steps == 0) return BeerModel.Action.STAND_STILL;
		else if (outputs[0] > outputs[1]) return BeerModel.Action.MOVE_LEFT;
		else return BeerModel.Action.MOVE_RIGHT;
	}

	public double[] convertDtod(Double[] list){
		double[] convert = new double[list.length]; 
		for (int i = 0; i < convert.length; i++) {
			convert[i] = list[i]; 
		}
		return convert; 
	}

//	@Override
//	public void showPhenotype(Phenotype phenotype) {
//		int countEatenAndDodged = 0;
//		int countMissedAndHits = 0;
//
//		for (BeerGame game : games) {
//			game.setDraw(true);
////			parameters.getGui().setMap(game.getGamePanel());
//			Double[] weights = ((BeerGenotype)phenotype.getGenotype()).getArrayGenotype(); 
//			NeuralNetwork network = new NeuralNetwork(parameters.getNetworkLayout());
//			network.setWeights(convertDtod(weights)); 
//
//			int iterations = parameters.getIterations();
//			for (int i = 0; i < iterations; i ++){
//				boolean[] sensors = game.getSensors();
//				double[] outputs = network.update(sensors); 
//				int steps = getSteps(outputs);
//				BeerModel.Action action = selectAction(outputs, steps);
//				
//				int moveResult = game.move(action, steps);
//
//				if (moveResult == 1){
//					countEatenAndDodged++;
//					System.out.println("CountEatenAndDodged: "+ countEatenAndDodged + "  countMissedAndHits: " + countMissedAndHits);
//				}
//				else if (moveResult == -1) {
//					countMissedAndHits++;
//					System.out.println("CountEatenAndDodged: "+ countEatenAndDodged + "  countMissedAndHits: " + countMissedAndHits);
//				}
//				
//				try {
//					Thread.sleep(parameters.getTimePerFrame());
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
//			}
//		}
//	}

	@Override
	public void nextGeneration() {
		// TODO Auto-generated method stub

	}

	public BeerGame[] getMaps(int i) {
		switch (i) {
		case INTERCEPT_TRAIN:
			return trainingIntercept;
		case DODGE_TRAIN:
			return trainingDodge;
		default:
			return games;
		}
	}

}
